从Node.js执行shell命令

您所在的位置:网站首页 windows bash命令 从Node.js执行shell命令

从Node.js执行shell命令

#从Node.js执行shell命令| 来源: 网络整理| 查看: 265

在这篇博文中,我们将探讨如何通过模块'node:child_process' ,从Node.js执行shell命令。

目录。

本博文的概述 Windows与Unix的对比 我们在例子中经常使用的功能 异步产生进程: spawn()

spawn()如何工作

什么时候执行shell命令?

仅有命令的模式与args模式

向子进程的stdin发送数据

手动进行管道输送

处理不成功的退出(包括错误)。

等待子进程的退出

终止子进程

同步地产生进程: spawnSync() 什么时候执行shell命令? 从stdout读取数据 向子进程的stdin发送数据 处理不成功的退出(包括错误) 基于 spawn() 的异步辅助函数 exec() execFile() 基于 spawnAsync() 的同步辅助函数 execSync() execFileSync() 有用的库 tinysh:一个用于生成shell命令的辅助工具 node-powershell:通过Node.js执行Windows PowerShell命令 如何在'node:child_process'模块的功能中选择? 进一步阅读 本博文概述

模块'node:child_process' 有一个执行shell命令的功能(在生成的子进程中),它有两个版本。

一个异步版本spawn() 。 一个同步版本spawnSync() 。

我们将首先探讨spawn() ,然后是spawnSync() 。最后,我们将看看以下基于它们的、相对类似的功能。

基于spawn() 。 exec() execFile() 基于spawnSync() 。 execSync() execFileSync() Windows与Unix的对比

这篇博文中显示的代码在Unix上运行,但我也在Windows上进行了测试--在Windows上,大部分代码只要稍作改动就可以使用(比如用'\r\n' 而不是'\n' 来结束行)。

我们在例子中经常使用的功能

以下功能经常出现在例子中。这就是为什么我们在这里解释一次。

断言:assert.equal() 用于原始值,assert.deepEqual() 用于对象。必要的导入在例子中从未显示。

import * as assert from 'node:assert/strict'; 复制代码

函数Readable.toWeb() 将Node的本地stream.Readable 转换为网络流(ReadableStream 的一个实例)。它在关于网络流的博文中解释了更多信息。Readable 在例子中总是被导入。

异步函数readableStreamToString() 消耗一个可读的网络流并返回一个字符串(用Promise包装)。它在关于网络流的博文中有解释](2ality.com/2022/06/web…

异步生成进程:spawn() spawn() 如何工作 spawn( command: string, args?: Array, options?: Object ): ChildProcess 复制代码

spawn()异步地在一个新的进程中执行一个命令。该进程与Node的主JavaScript进程同时运行,我们可以通过各种方式(通常是通过流)与它通信。

接下来,有关于spawn() 的参数和结果的文档。如果你喜欢通过例子来学习,你可以跳过这些内容,继续下面的小节。

参数:command

command 是一个带有shell命令的字符串。有两种使用这个参数的模式。

纯命令模式:args 被省略,command 包含整个 shell 命令。我们甚至可以使用shell的功能,如在多个可执行文件之间进行管道连接,将I/O重定向到文件、变量和通配符。 options.shell 必须是 ,因为我们需要一个shell来处理shell的功能。true Args模式:command 只包含命令的名称,args 包含其参数。 如果options.shell 是true ,参数里面的许多元字符都会被解释,诸如通配符和变量名等功能都会起作用。 如果options.shell 是false ,字符串将被逐字使用,我们不必转义元字符。

这两种模式将在本篇文章的后面演示。

参数:options

下面的options 是最有趣的。

.shell: boolean|string (默认: )false 是否应该使用shell来执行命令?

在Windows上,这个选项几乎都应该是 。例如, 和 文件不能以其他方式执行。true .bat .cmd 在Unix上,如果 是 ,只有核心shell功能(例如管道、I/O重定向、文件名通配符和变量)不能使用。.shell false 如果 是 ,我们必须小心处理用户输入,对其进行消毒,因为这很容易执行任意代码。如果我们想把元字符作为非元字符使用,我们还必须转义元字符。.shell true 我们也可以把 设置为一个shell可执行文件的路径。然后Node.js使用该可执行文件来执行命令。如果我们把 设为 ,Node.js就会使用:.shell .shell true

Unix。

'/bin/sh'

Windows。

process.env.ComSpec

.cwd: string | URL 指定执行命令时要使用的当前工作目录(CWD)。

.stdio: Array|string 配置标准I/O的设置方式。这在下面有解释。

.env: Object (默认: )process.env 让我们为子进程指定shell变量。提示:

查看 (例如在Node.js REPL中),看看有哪些变量存在。process.env

我们可以使用spreading来非破坏性地覆盖一个现有的变量--或者在它还不存在的时候创建它:

{env: {...process.env, MY_VAR: 'Hi!'}} 复制代码

.signal: AbortSignal 如果我们创建一个AbortControllerac ,我们可以将ac.signal 传递给spawn() ,并通过ac.abort() 终止子进程。这将在本篇文章的后面进行演示。

.timeout: number 如果子进程的时间超过.timeout milliseconds,它就会被杀死。

options.stdio

子进程的每个标准I/O流都有一个数字ID,即所谓的文件描述符。

标准输入(stdin)的文件描述符为0。 标准输出(stdout)有一个文件描述符1。 标准错误(stderr)的文件描述符为2。

可以有更多的文件描述符,但这很罕见。

options.stdio 配置子进程的流是否以及如何被输送到父进程的流中。它可以是一个数组,每个元素配置与其索引相等的文件描述符。下面的值可以作为数组元素使用。

'pipe':

索引0:将childProcess.stdin 到子进程的stdin。注意,尽管它的名字,前者是一个属于父进程的流。 索引1:将子进程的stdout管道到childProcess.stdout 。 索引2:将子进程的stder发送到childProcess.stderr 。

'ignore':忽略子进程的流。

'inherit':将子进程的流管到父进程的相应流中。

例如,如果我们希望子进程的stderr被记录到控制台,我们可以在索引2处使用'inherit' 。

本地Node.js流。管到或来自该流。

也支持其他值,但这超出了本帖的范围。

我们也可以不通过数组来指定options.stdio ,而是缩写。

'pipe' 相当于 (默认的是 )。['pipe', 'pipe', 'pipe'] options.stdio 'ignore' 相当于 。['ignore', 'ignore', 'ignore'] 'inherit' 相当于 。['inherit', 'inherit', 'inherit'] 结果:ChildProcess 的实例

spawn() 返回的实例 ChildProcess.

有趣的数据属性。

.exitCode: number | null 包含子进程退出时的代码。 0(零)表示正常退出。 大于0的数字意味着发生了一个错误。 null 表示该进程还没有退出。 .signalCode: string | null 子进程被杀死的POSIX信号,如果没有被杀死,则null 。更多信息见下面对方法.kill() 的描述。 流。根据标准I/O的配置方式(见前一小节),以下流是可用的。 .stdin .stdout .stderr .pid: number | undefined 子进程的进程标识符(PID)。如果催生失败,.pid 是undefined 。这个值在调用spawn() 后立即可用。

有趣的方法。

.kill(signalCode?: number | string = 'SIGTERM'): boolean 向子进程发送一个POSIX信号(通常会导致进程的终止)。

signal的man page包含了一个值的列表。 Windows不支持信号,但Node.js模拟了其中的一些信号--例如。SIGINT,SIGTERM, 和SIGKILL 。欲了解更多信息,请参见Node.js文档。

这个方法将在本篇文章的后面演示。

有趣的事件。

.on('exit', (exitCode: number|null, signalCode: string|null) => {}) 这个事件在子进程结束后发出。 回调参数为我们提供了退出代码或信号代码。其中一个将永远是非空的。 它的一些标准I/O流可能仍然是开放的,因为多个进程可能共享相同的流。当一个子进程退出后所有的stdio流被关闭时,事件'close' 通知我们。 .on('error', (err: Error) => {}) 如果一个进程不能被生成(见后面的例子)或者子进程不能被杀死,这个事件最常被发出来。在这个事件之后,可能会也可能不会发出一个'exit' 事件。

我们将在后面看到事件如何被转化为可以被等待的承诺。

什么时候执行shell命令?

当使用异步spawn() ,命令的子进程是异步启动的。下面的代码演示了这一点。

import {spawn} from 'node:child_process'; spawn( 'echo', ['Command starts'], { stdio: 'inherit', shell: true, } ); console.log('After spawn()'); 复制代码

这就是输出结果。

After spawn() Command starts 复制代码 纯命令模式与args模式

在本节中,我们以两种方式指定同一命令的调用。

只用命令的模式。我们通过第一个参数command ,提供整个调用的内容。 Args模式。我们通过第一个参数command 提供命令,通过第二个参数args 提供其参数。 仅限命令模式 import {Readable} from 'node:stream'; import {spawn} from 'node:child_process'; const childProcess = spawn( 'echo "Hello, how are you?"', { shell: true, // (A) stdio: ['ignore', 'pipe', 'inherit'], // (B) } ); const stdout = Readable.toWeb( childProcess.stdout.setEncoding('utf-8')); // Result on Unix assert.equal( await readableStreamToString(stdout), 'Hello, how are you?\n' // (C) ); // Result on Windows: '"Hello, how are you?"\r\n' 复制代码

true 每一个带参数的纯命令式催生都需要.shell (A行)--即使像这个一样简单。

在B行,我们告诉spawn() 如何处理标准I/O。

忽略标准输入。 将子进程的stdout管到childProcess.stdout (一个属于父进程的流)。 把子进程的stderr转到父进程的stderr。

在这种情况下,我们只对子进程的输出感兴趣。因此,一旦我们处理完输出,我们就完成了。在其他情况下,我们可能需要等待,直到子进程退出。如何做到这一点,将在后面演示。

在纯命令模式下,我们可以看到更多的shell的奇特之处--例如,Windows命令shell的输出包括双引号(最后一行)。

Args模式 import {Readable} from 'node:stream'; import {spawn} from 'node:child_process'; const childProcess = spawn( 'echo', ['Hello, how are you?'], { shell: true, stdio: ['ignore', 'pipe', 'inherit'], } ); const stdout = Readable.toWeb( childProcess.stdout.setEncoding('utf-8')); // Result on Unix assert.equal( await readableStreamToString(stdout), 'Hello, how are you?\n' ); // Result on Windows: 'Hello, how are you?\r\n' 复制代码 args 中的元字符

让我们来探讨一下如果args 中有元字符会发生什么。

import {Readable} from 'node:stream'; import {spawn} from 'node:child_process'; async function echoUser({shell, args}) { const childProcess = spawn( `echo`, args, { stdio: ['ignore', 'pipe', 'inherit'], shell, } ); const stdout = Readable.toWeb( childProcess.stdout.setEncoding('utf-8')); return readableStreamToString(stdout); } // Results on Unix assert.equal( await echoUser({shell: false, args: ['$USER']}), // (A) '$USER\n' ); assert.equal( await echoUser({shell: true, args: ['$USER']}), // (B) 'rauschma\n' ); assert.equal( await echoUser({shell: true, args: [String.raw`\$USER`]}), // (C) '$USER\n' ); 复制代码 如果我们不使用shell,元字符如美元符号($)就没有影响(A行)。 如果使用shell,$USER 会被解释为一个变量(B行)。 如果我们不希望这样,我们必须通过反斜杠转义美元符号(C行)。

类似的效果也发生在其他元字符上,如星号(*)。

这些是Unix shell元字符的两个例子。Windows shell有自己的元字符和自己的转义方式。

一个更复杂的shell命令

让我们使用更多的shell功能(这需要只用命令模式)。

import {Readable} from 'node:stream'; import {spawn} from 'node:child_process'; import {EOL} from 'node:os'; const childProcess = spawn( `(echo cherry && echo apple && echo banana) | sort`, { stdio: ['ignore', 'pipe', 'inherit'], shell: true, } ); const stdout = Readable.toWeb( childProcess.stdout.setEncoding('utf-8')); assert.equal( await readableStreamToString(stdout), 'apple\nbanana\ncherry\n' ); 复制代码 向子进程的stdin发送数据

到目前为止,我们只读取了子进程的标准输出。但是我们也可以向标准输入发送数据。

import {Readable, Writable} from 'node:stream'; import {spawn} from 'node:child_process'; const childProcess = spawn( `sort`, // (A) { stdio: ['pipe', 'pipe', 'inherit'], } ); const stdin = Writable.toWeb(childProcess.stdin); // (B) const writer = stdin.getWriter(); // (C) try { await writer.write('Cherry\n'); await writer.write('Apple\n'); await writer.write('Banana\n'); } finally { writer.close(); } const stdout = Readable.toWeb( childProcess.stdout.setEncoding('utf-8')); assert.equal( await readableStreamToString(stdout), 'Apple\nBanana\nCherry\n' ); 复制代码

我们使用shell命令sort (A行)来为我们对文本行进行排序。

在B行,我们使用Writable.toWeb() ,将一个原生的Node.js流转换为一个网络流(更多信息请参见关于网络流的博文)。

如何通过写入器向WritableStream写入(C行),在关于网络流的博文中也有解释。

手动管道

我们之前让一个shell执行以下命令。

(echo cherry && echo apple && echo banana) | sort 复制代码

在下面的例子中,我们手动做管道,从echoes(A行)到排序(B行)。

import {Readable, Writable} from 'node:stream'; import {spawn} from 'node:child_process'; const echo = spawn( // (A) `echo cherry && echo apple && echo banana`, { stdio: ['ignore', 'pipe', 'inherit'], shell: true, } ); const sort = spawn( // (B) `sort`, { stdio: ['pipe', 'pipe', 'inherit'], shell: true, } ); //==== Transferring chunks from echo.stdout to sort.stdin ==== const echoOut = Readable.toWeb( echo.stdout.setEncoding('utf-8')); const sortIn = Writable.toWeb(sort.stdin); const sortInWriter = sortIn.getWriter(); try { for await (const chunk of echoOut) { // (C) await sortInWriter.write(chunk); } } finally { sortInWriter.close(); } //==== Reading sort.stdout ==== const sortOut = Readable.toWeb( sort.stdout.setEncoding('utf-8')); assert.equal( await readableStreamToString(sortOut), 'apple\nbanana\ncherry\n' ); 复制代码

可读流(ReadableStreams),如echoOut ,是可以异步迭代的。这就是为什么我们可以使用for-await-of 循环来读取它们的块(流数据的片段)。欲了解更多信息,请参见关于网络流的博文。

处理不成功的退出(包括错误)

主要有三种不成功的退出。

子进程不能被催生。 shell中发生错误。 一个进程被杀死。 子进程不能被催生

下面的代码演示了如果一个子进程不能被催生会发生什么。在这种情况下,原因是shell的路径没有指向一个可执行文件(A行)。

import {spawn} from 'node:child_process'; const childProcess = spawn( 'echo hello', { stdio: ['inherit', 'inherit', 'pipe'], shell: '/bin/does-not-exist', // (A) } ); childProcess.on('error', (err) => { // (B) assert.equal( err.toString(), 'Error: spawn /bin/does-not-exist ENOENT' ); }); 复制代码

这是我们第一次使用事件来处理子进程。在B行,我们为'error' 事件注册了一个事件监听器。子进程在当前代码片段完成后启动。这有助于防止竞赛条件。当我们开始监听时,我们可以确定该事件还没有被发射出来。

在shell中发生了一个错误

如果shell代码包含一个错误,我们不会得到一个'error' 事件(B行),我们会得到一个带有非零退出代码的'exit' 事件(A行)。

import {Readable} from 'node:stream'; import {spawn} from 'node:child_process'; const childProcess = spawn( 'does-not-exist', { stdio: ['inherit', 'inherit', 'pipe'], shell: true, } ); childProcess.on('exit', async (exitCode, signalCode) => { // (A) assert.equal(exitCode, 127); assert.equal(signalCode, null); const stderr = Readable.toWeb( childProcess.stderr.setEncoding('utf-8')); assert.equal( await readableStreamToString(stderr), '/bin/sh: does-not-exist: command not found\n' ); } ); childProcess.on('error', (err) => { // (B) console.error('We never get here!'); }); 复制代码 一个进程被杀死

如果一个进程在Unix上被杀死,退出代码是null (C行),信号代码是一个字符串(D行)。

import {Readable} from 'node:stream'; import {spawn} from 'node:child_process'; const childProcess = spawn( 'kill $$', // (A) { stdio: ['inherit', 'inherit', 'pipe'], shell: true, } ); console.log(childProcess.pid); // (B) childProcess.on('exit', async (exitCode, signalCode) => { assert.equal(exitCode, null); // (C) assert.equal(signalCode, 'SIGTERM'); // (D) const stderr = Readable.toWeb( childProcess.stderr.setEncoding('utf-8')); assert.equal( await readableStreamToString(stderr), '' // (E) ); }); 复制代码

请注意,没有错误输出(E行)。

与其让子进程自己杀死自己(A行),我们还可以让它暂停更长的时间,通过B行记录的进程ID手动杀死它。

如果我们在Windows上杀死一个子进程会发生什么?

exitCode 是 。1 signalCode 是 。null 等待一个子进程的退出

有时我们只想等待一个命令的完成。这可以通过事件和承诺来实现。

通过事件等待 import * as fs from 'node:fs'; import {spawn} from 'node:child_process'; const childProcess = spawn( `(echo first && echo second) > tmp-file.txt`, { shell: true, stdio: 'inherit', } ); childProcess.on('exit', (exitCode, signalCode) => { // (A) assert.equal(exitCode, 0); assert.equal(signalCode, null); assert.equal( fs.readFileSync('tmp-file.txt', {encoding: 'utf-8'}), 'first\nsecond\n' ); }); 复制代码

我们使用标准的Node.js事件模式,为'exit' 事件注册一个监听器(A行)。

通过承诺等待 import * as fs from 'node:fs'; import {spawn} from 'node:child_process'; const childProcess = spawn( `(echo first && echo second) > tmp-file.txt`, { shell: true, stdio: 'inherit', } ); const {exitCode, signalCode} = await onExit(childProcess); // (A) assert.equal(exitCode, 0); assert.equal(signalCode, null); assert.equal( fs.readFileSync('tmp-file.txt', {encoding: 'utf-8'}), 'first\nsecond\n' ); 复制代码

我们在A行使用的辅助函数onExit() ,返回一个Promise,如果'exit' 事件被发出,这个Promise就会被履行。

export function onExit(eventEmitter) { return new Promise((resolve, reject) => { eventEmitter.once('exit', (exitCode, signalCode) => { if (exitCode === 0) { // (B) resolve({exitCode, signalCode}); } else { reject(new Error( `Non-zero exit: code ${exitCode}, signal ${signalCode}`)); } }); eventEmitter.once('error', (err) => { // (C) reject(err); }); }); } 复制代码

如果eventEmitter 失败,返回的Promise被拒绝,await 在A行抛出一个异常。onExit() 处理两种失败。

exitCode 不为零(B行)。这就发生了。

如果有一个shell错误。那么exitCode 是大于零的。 如果子进程在Unix上被杀死。那么exitCode 是null ,signalCode 是非空的。 在Windows上杀死子进程会产生一个shell错误。

一个'error' 事件被发射出来(C行)。这发生在子进程不能被生成的情况下。

终止子进程 通过AbortController终止一个子进程

在这个例子中,我们使用一个AbortController来终止一个shell命令。

import {spawn} from 'node:child_process'; const abortController = new AbortController(); // (A) const childProcess = spawn( `echo Hello`, { stdio: 'inherit', shell: true, signal: abortController.signal, // (B) } ); childProcess.on('error', (err) => { assert.equal( err.toString(), 'AbortError: The operation was aborted' ); }); abortController.abort(); // (C) 复制代码

我们创建一个AbortController(A行),将其信号传递给spawn() (B行),并通过AbortController(C行)终止shell命令。

子进程是异步启动的(在当前代码片段执行完毕后)。这就是为什么我们可以在进程开始之前就终止,以及为什么我们在这种情况下看不到任何输出。

通过.kill() 终止一个子进程

在下一个例子中,我们通过方法.kill() (最后一行)终止一个子进程。

import {spawn} from 'node:child_process'; const childProcess = spawn( `echo Hello`, { stdio: 'inherit', shell: true, } ); childProcess.on('exit', (exitCode, signalCode) => { assert.equal(exitCode, null); assert.equal(signalCode, 'SIGTERM'); }); childProcess.kill(); // default argument value: 'SIGTERM' 复制代码

再一次,我们在子进程启动之前就杀死了它(异步的!),而且没有输出。

同步地产生进程:spawnSync() spawnSync( command: string, args?: Array, options?: Object ): Object 复制代码

spawnSync()是spawn() 的同步版本--它等到子进程退出后才同步(!)返回一个对象。

参数大多与 spawn() 相同。options 有一些额外的属性--例如。

.input: string | TypedArray | DataView 如果这个属性存在,它的值会被发送到子进程的标准输入。 .encoding: string (默认: )'buffer' 指定所有标准I/O流使用的编码。

该函数返回一个对象。它最有趣的属性是。

.stdout: Buffer | string 包含写到子进程的标准输出流的内容。 .stderr: Buffer | string 包含写到子进程的标准错误流中的内容。 .status: number | null 包含子进程的退出代码或null 。退出代码或信号代码都是非空的。 .signal: string | null 包含子进程的信号代码或null 。退出代码或信号代码都不是空的。 .error?: Error 这个属性只有在产卵没有成功时才会被创建,然后包含一个错误对象。

使用异步spawn() ,子进程并发运行,我们可以通过流读取标准I/O。相比之下,同步的spawnSync() ,收集流的内容并同步返回给我们(见下一小节)。

shell命令何时执行?

当使用同步spawnSync() ,命令的子进程是同步启动的。下面的代码演示了这一点。

import {spawnSync} from 'node:child_process'; spawnSync( 'echo', ['Command starts'], { stdio: 'inherit', shell: true, } ); console.log('After spawnSync()'); 复制代码

这就是输出。

Command starts After spawnSync() 复制代码 从stdout读取

下面的代码演示了如何读取标准输出。

import {spawnSync} from 'node:child_process'; const result = spawnSync( `echo rock && echo paper && echo scissors`, { stdio: ['ignore', 'pipe', 'inherit'], // (A) encoding: 'utf-8', // (B) shell: true, } ); console.log(result); assert.equal( result.stdout, // (C) 'rock\npaper\nscissors\n' ); assert.equal(result.stderr, null); // (D) 复制代码

在A行,我们用options.stdio ,告诉spawnSync() ,我们只对标准输出感兴趣。我们忽略了标准输入,并将标准错误输送到父进程。

因此,我们只得到一个标准输出的结果属性(C行),标准错误的属性是null (D行)。

由于我们不能访问spawnSync() 内部用来处理子进程的标准I/O的流,我们通过options.encoding (B行)告诉它要使用哪种编码。

向子进程的stdin发送数据

我们可以通过选项属性.input (A行)向子进程的标准输入流发送数据。

import {spawnSync} from 'node:child_process'; const result = spawnSync( `sort`, { stdio: ['pipe', 'pipe', 'inherit'], encoding: 'utf-8', input: 'Cherry\nApple\nBanana\n', // (A) } ); assert.equal( result.stdout, 'Apple\nBanana\nCherry\n' ); 复制代码 处理不成功的退出(包括错误)

主要有三种不成功的退出(当退出代码不为零时)。

子进程不能被生成。 shell中发生了一个错误。 一个进程被杀死。 子进程不能被催生

如果产卵失败,spawn() 会发出一个'error' 事件。相反,spawnSync() 将result.error 设置为一个错误对象。

import {spawnSync} from 'node:child_process'; const result = spawnSync( 'echo hello', { stdio: ['ignore', 'inherit', 'pipe'], encoding: 'utf-8', shell: '/bin/does-not-exist', } ); assert.equal( result.error.toString(), 'Error: spawnSync /bin/does-not-exist ENOENT' ); 复制代码 在shell中发生了一个错误

如果在shell中发生错误,退出代码result.status 大于0,result.signal 为null 。

import {spawnSync} from 'node:child_process'; const result = spawnSync( 'does-not-exist', { stdio: ['ignore', 'inherit', 'pipe'], encoding: 'utf-8', shell: true, } ); assert.equal(result.status, 127); assert.equal(result.signal, null); assert.equal( result.stderr, '/bin/sh: does-not-exist: command not found\n' ); 复制代码 一个进程被杀死

如果子进程在Unix上被杀死,result.signal 包含信号的名称,result.status 是null 。

import {spawnSync} from 'node:child_process'; const result = spawnSync( 'kill $$', { stdio: ['ignore', 'inherit', 'pipe'], encoding: 'utf-8', shell: true, } ); assert.equal(result.status, null); assert.equal(result.signal, 'SIGTERM'); assert.equal(result.stderr, ''); // (A) 复制代码

注意,没有输出被发送到标准错误流(A行)。

如果我们在Windows上杀死一个子进程

result.status 是1 result.signal 是null result.stderr 是'' 基于spawn() 的异步辅助函数

在本节中,我们看一下模块node:child_process 中的两个异步函数,它们是基于spawn() 。

exec() execFile()

我们在这篇博文中忽略了 fork()在这篇博文中。引用Node.js的文档。

fork() 生成一个新的Node.js进程,并调用一个指定的模块,建立一个IPC通信通道,允许在父子之间发送消息。

exec() exec( command: string, options?: Object, callback?: (error, stdout, stderr) => void ): ChildProcess 复制代码

exec()在一个新产生的shell中运行一个命令。与spawn() 的主要区别在于。

除了返回一个ChildProcess外,exec() 还通过回调提供一个结果。要么是一个错误对象,要么是stdout和stderr的内容。 错误的原因:子进程不能被生成,shell错误,子进程被杀死。 相比之下,spawn() 只在子进程不能被生成的情况下发出'error' 事件。其他两种故障是通过退出代码和(在Unix中)信号代码来处理。 没有参数args 。 options.shell 的默认值是true 。 import {exec} from 'node:child_process'; const childProcess = exec( 'echo Hello', (error, stdout, stderr) => { if (error) { console.error('error: ' + error.toString()); return; } console.log('stdout: ' + stdout); // 'stdout: Hello\n' console.error('stderr: ' + stderr); // 'stderr: ' } ); 复制代码

exec() 可以通过以下方式转换为基于Promise的函数 util.promisify():

ChildProcess成为返回的Promise的一个属性。 Promise的解决方式如下。 实现值:{stdout, stderr} 拒绝值:与回调的参数error 相同的值,但有两个额外的属性:.stdout 和.stderr 。 import * as util from 'node:util'; import * as child_process from 'node:child_process'; const execAsync = util.promisify(child_process.exec); try { const resultPromise = execAsync('echo Hello'); const {childProcess} = resultPromise; const obj = await resultPromise; console.log(obj); // { stdout: 'Hello\n', stderr: '' } } catch (err) { console.error(err); } 复制代码 execFile()

execFile(file, args?, options?, callback?): ChildProcess

工作原理类似于exec() ,有以下区别。

支持参数args 。 options.shell 的默认值是false 。

像exec() 一样,execFile() 可以通过util.promisify() 转换为基于承诺的函数。

基于spawnAsync() 的同步辅助函数 execSync() execSync( command: string, options?: Object ): Buffer | string 复制代码

execSync()在一个新的子进程中运行一个命令并同步等待,直到该进程退出。与spawnSync() 的主要区别是。

只返回stdout的内容。 通过异常报告三种故障:子进程不能被生成,shell错误,子进程被杀死。 相比之下,spawnSync() 的结果只有在子进程不能被催生的情况下才有.error 的属性。其他两种故障是通过退出代码和(在Unix上)信号代码处理的。 没有参数args 。 options.shell 的默认值是true 。 import {execSync} from 'node:child_process'; try { const stdout = execSync('echo Hello'); console.log('stdout: ' + stdout); // 'stdout: Hello\n' } catch (err) { console.error('Error: ' + err.toString()); } 复制代码 execFileSync()

execFileSync(file, args?, options?): Buffer | string

工作原理与execSync() 类似,但有以下区别。

支持参数args 。 options.shell 的默认值是false 。 有用的库 tinysh:生成shell命令的辅助工具

tinysh由Anton Medvedev编写,是一个帮助生成shell命令的小库--例如。

import sh from 'tinysh'; console.log(sh.ls('-l')); console.log(sh.cat('README.md')); 复制代码

我们可以通过使用.call() ,将一个对象作为this ,来覆盖默认的选项。

sh.tee.call({input: 'Hello, world!'}, 'file.txt'); 复制代码

我们可以使用任何属性名称,tinysh就会以该名称执行shell命令。它通过一个代理(Proxy)来实现这一壮举。这是实际库的一个稍加修改的版本。

import {execFileSync} from 'node:child_process'; const sh = new Proxy({}, { get: (_, bin) => function (...args) { // (A) return execFileSync(bin, args, { encoding: 'utf-8', shell: true, ...this // (B) } ); }, }); 复制代码

在A行,我们可以看到,如果我们从sh ,得到一个名称为bin 的属性,就会返回一个函数,调用execFileSync() ,并使用bin 作为第一个参数。

在B行中展开this ,使我们能够通过.call() ,指定选项。默认值排在前面,这样就可以通过this 来覆盖它们。

node-powershell:通过Node.js执行Windows PowerShell命令

在Windows上使用node-powershell库,看起来如下。

import { PowerShell } from 'node-powershell'; PowerShell.$`echo "hello from PowerShell"`; 复制代码 如何选择模块的功能'node:child_process'

一般约束。

在执行命令的同时,其他异步任务是否应该运行? 使用任何异步函数。 一次只执行一个命令吗(后台没有异步任务)? 使用任何同步函数。 你想通过一个流访问子进程的stdin或stdout吗? 只有异步函数可以让你访问流:spawn() 在这种情况下更简单,因为它没有一个传递错误和标准I/O内容的回调。 你想在一个字符串中捕获stdout或stderr吗? 异步选项:exec() 和execFile() 同步选项。spawnSync(),execSync() 。execFileSync()

异步函数--在spawn() 和exec() 或execFile() 之间选择。

exec() 和 ,有两个好处。execFile() 失败更容易处理,因为它们都以同样的方式报告--通过第一个回调参数。 获取stdout和stderr作为字符串更容易--由于回调的关系。 如果这些好处对你来说并不重要,你可以选择spawn() 。它的签名更简单,没有(可选的)回调。

同步函数--在spawnSync() 和execSync() 或execFileSync() 中选择。

execSync() 和 有两个特长。execFileSync() 它们返回一个带有stdout内容的字符串。 失败更容易处理,因为它们都是以同样的方式报告的--通过异常。 如果你需要比execSync() 和execFileSync() 通过其返回值和异常提供更多的信息,请选择spawnSync() 。

在exec() 和execFile() 之间进行选择(同样的参数适用于在execSync() 和execFileSync() 之间进行选择)。

options.shell 的默认值是exec() 中的true ,但execFile() 中的false 。 execFile() 支持 , 则不支持。args exec() 进一步阅读 在Node.js上使用网络流 在Node.js上使用文件系统


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3